#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <uv.h>

#define CAT_BUF_SIZE 128

struct uvcat_context {
    uv_loop_t *loop;
    uv_fs_t open_req;
    uv_fs_t read_req;
    uv_fs_t write_req;
    uv_fs_t close_req;
    uv_buf_t iov;
};

void uvcat_context_init(struct uvcat_context *ctx, uv_loop_t *loop) {
    ctx->loop = loop;
    ctx->open_req.data = ctx;
    ctx->read_req.data = ctx;
    ctx->write_req.data = ctx;
    ctx->close_req.data = ctx;
    char *buffer = malloc(CAT_BUF_SIZE);
    ctx->iov = uv_buf_init(buffer, CAT_BUF_SIZE);
}

void uvcat_context_cleanup(struct uvcat_context *ctx) {
    if (!ctx || !ctx->iov.base) {
        return;
    }
    free(ctx->iov.base);
    ctx->iov.base = NULL;
    ctx->iov.len = 0;
    uv_fs_req_cleanup(&ctx->open_req);
    uv_fs_req_cleanup(&ctx->read_req);
    uv_fs_req_cleanup(&ctx->write_req);
    uv_fs_req_cleanup(&ctx->close_req);
}

static void close_cb(uv_fs_t *req) {
    struct uvcat_context *ctx = req->data;
    assert(req == &ctx->close_req);
    if (req->result == 0) {
        fprintf(stderr, "\nFile closed\n");
    } else {
        fprintf(stderr, "Error closing file: %s\n", uv_strerror(req->result));
    }
}

static void read_cb(uv_fs_t *req);
static void write_cb(uv_fs_t *req) {
    struct uvcat_context *ctx = req->data;
    assert(req == &ctx->write_req);
    if (req->result >= 0) {
        uv_fs_read(ctx->loop, &ctx->read_req, ctx->open_req.result, &ctx->iov, 1, -1, read_cb);
    } else {
        fprintf(stderr, "Error writing file: %s\n", uv_strerror(req->result));
        uv_fs_close(ctx->loop, &ctx->close_req, ctx->open_req.result, close_cb);
    }
}

static void read_cb(uv_fs_t *req) {
    struct uvcat_context *ctx = req->data;
    assert(req == &ctx->read_req);
    if (req->result > 0) {
        ctx->iov.len = req->result;
        uv_fs_write(ctx->loop, &ctx->write_req, STDOUT_FILENO, &ctx->iov, 1, -1, write_cb);
    } else if (req->result == 0) {
        // EOF
        uv_fs_close(ctx->loop, &ctx->close_req, ctx->open_req.result, close_cb);
    } else {
        fprintf(stderr, "Error reading file: %s\n", uv_strerror(req->result));
        uv_fs_close(ctx->loop, &ctx->close_req, ctx->open_req.result, close_cb);
    }
}

static void open_cb(uv_fs_t *req) {
    struct uvcat_context *ctx = req->data;
    assert(req == &ctx->open_req);
    if (req->result >= 0) {
        uv_fs_read(ctx->loop, &ctx->read_req, req->result, &ctx->iov, 1, -1, read_cb);
    } else {
        fprintf(stderr, "Error opening file: %s\n", uv_strerror(req->result));
    }
}

int main(int argc, char **argv) {
    struct uvcat_context ctx;
    uvcat_context_init(&ctx, uv_default_loop());

    if (argc < 2) {
        fprintf(stderr, "Usage: %s <file>\n", argv[0]);
        return 1;
    }

    uv_fs_open(ctx.loop, &ctx.open_req, argv[1], O_RDONLY, 0, open_cb);

    uv_run(ctx.loop, UV_RUN_DEFAULT);

    uvcat_context_cleanup(&ctx);

    uv_loop_close(ctx.loop);
    ctx.loop = NULL;

    return 0;
}